# Electron SDK 配置参考

## SDK 基础配置

| 参数 | 类型 | 描述 | 是否必填 | 默认值 |
|------|------|------|----------|--------|
| `endpoint` | `string` | 数据上报地址 | 是 | — |
| `enable` | `boolean` | 是否启用 SDK，关闭后所有采集器与上报均不工作 | 否 | `true` |
| `env` | `'prod' \| 'gray' \| 'pre' \| 'daily' \| 'local' \| string` | 应用环境标识 | 否 | — |
| `version` | `string` | 应用版本号 | 否 | — |

> `endpoint` 为完整上报地址 URL，可在 ARMS 控制台「用户体验监控 > 应用列表」创建应用后获取。

```typescript
import armsRum from '@arms/rum-electron';

armsRum.init({
  endpoint: '<your-endpoint>',
  enable: true,
  env: 'prod',
  version: '1.0.0',
});
```

---

## app 配置

应用扩展信息，便于在 ARMS 控制台按维度筛选与聚合。

| 参数 | 类型 | 描述 |
|------|------|------|
| `app.id` | `string` | 应用唯一标识 |
| `app.name` | `string` | 应用名称 |
| `app.version` | `string` | 应用版本（与顶层 `version` 独立） |
| `app.channel` | `string` | 发布渠道 |
| `app.env` | `string` | 应用环境 |
| `app.type` | `string` | 应用类型 |
| `app.package` | `string` | 包名 |
| `app.framework` | `string` | 技术框架 |

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  app: {
    name: 'MyElectronApp',
    version: '2.1.0',
    channel: 'stable',
    env: 'prod',
    type: 'electron',
    package: 'com.example.my-app',
    framework: 'react',
  },
});
```

---

## user 配置

用户信息，便于在控制台按用户维度排查问题。

| 参数 | 类型 | 描述 |
|------|------|------|
| `user.id` | `string` | 用户 ID |
| `user.name` | `string` | 用户名称 |
| `user.tags` | `string` | 用户标签 |

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  user: { id: 'u_12345', name: '张三', tags: 'vip,enterprise' },
});
```

---

## sessionConfig 配置

会话（Session）的采样与生命周期策略。

| 参数 | 类型 | 描述 | 默认值 |
|------|------|------|--------|
| `sampleRate` | `number` | 会话采样率，取值 **0–1** | `1` |
| `maxDuration` | `number` | 会话最大持续时间（ms） | — |
| `overtime` | `number` | 会话无活动超时时间（ms） | — |

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  sessionConfig: {
    sampleRate: 1,
    maxDuration: 4 * 60 * 60 * 1000,  // 最长 4 小时
    overtime: 30 * 60 * 1000,          // 30 分钟无活动则超时
  },
});
```

---

## reportConfig 配置

上报节奏与重试策略。

| 参数 | 类型 | 描述 |
|------|------|------|
| `flushTime` | `number` | 上报 flush 间隔（ms） |
| `maxEventCount` | `number` | 单次上报最大事件数 |
| `maxRetryCount` | `number` | 最大重试次数 |
| `retryDelay` | `number` | 重试延迟（ms） |

---

## remoteConfig 配置

远程配置动态管控。也可直接传 `remoteConfig: true` 开启默认远程配置。

| 参数 | 类型 | 描述 | 默认值 |
|------|------|------|--------|
| `enable` | `boolean` | 是否启用远程配置 | `false` |
| `url` | `string` | 配置服务器 URL；未指定时 SDK 从 `endpoint` 自动推导 | — |
| `mode` | `'launch-first' \| 'remote-first'` | `launch-first` 先用本地配置启动，异步拉取远端；`remote-first` 阻塞等待云端配置，超时降级到本地 | `'launch-first'` |
| `cacheTimeout` | `number` | 本地配置缓存有效期（ms） | `3600000`（1 小时） |

---

## collectors 配置（主进程采集器）

主进程各采集器的启用/禁用开关。每个采集器支持 `boolean` 或 `ICollectorConfig` 对象细化配置。

| 采集器 | 类型 | 描述 | 默认值 |
|--------|------|------|--------|
| `jsError` | `boolean \| ICollectorConfig` | 主进程未捕获异常 + 未处理 Promise 拒绝 | `true` |
| `consoleError` | `boolean \| ICollectorConfig` | `console.error` 拦截上报 | `true` |
| `crash` | `boolean \| ICollectorConfig` | 原生崩溃采集（依赖 `crashReporter`，WASM minidump 解析） | `true` |
| `application` | `boolean \| ICollectorConfig` | 应用启动指标（`app ready` 耗时、进程数、主进程 CPU/内存） | `true` |
| `memory` | `boolean \| IMemoryCollectorConfig` | 运行时内存水位（10s 后台采样 / 30min 窗口聚合 + crash/quit 触发） | `false` |
| `api` | `boolean \| ICollectorConfig` | 主进程 HTTP 请求采集：`globalThis.fetch` 全局 patch + `http`/`https` 模块 patch（覆盖 axios 默认 adapter 等），事件 `type='api'` | `true` |
| `rpc` | `boolean \| ICollectorConfig` | tRPC server middleware（配合 `armsRum.instrumentTRPC()`），事件 `type='rpc'` | `true` |
| `anr` | `boolean \| IAnrCollectorConfig` | 应用未响应监控（主进程 setTimeout 探针 + 渲染进程心跳超时；Electron >= 34 可采集渲染进程调用栈） | `false` |

`ICollectorConfig` 通用字段：

| 字段 | 类型 | 描述 |
|------|------|------|
| `enable` | `boolean` | 是否启用 |
| `sampling` | `number` | 采样率 |
| `filters` | `MatchOption[]` | 过滤规则；命中即跳过采集。`api` 按 URL 匹配，`rpc` 按 procedure path 匹配 |

> `MatchOption` 支持三种形式：`string`（URL/path 包含匹配）、`RegExp`（正则）、`(value: string) => boolean`（自定义函数）。

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  collectors: {
    jsError: true,
    consoleError: false,        // 关闭 console.error 拦截
    crash: true,
    application: true,
    api: {
      enable: true,
      filters: [/\.internal\.example\.com/],   // 命中即跳过
    },
    rpc: {
      enable: true,
      filters: [/^internal\./],                // 按 procedure path 跳过
    },
    memory: {
      enable: true,
      interval: 1_800_000,                     // 窗口聚合上报间隔 ms（最小值 30min）
      onLaunch: true,                           // app ready 时发出启动基线事件
      onInterval: true,                         // 启用窗口聚合周期采集
      onCrash: true,                            // render/child-process-gone 时 flush
      // crashReasons: ['crashed', 'oom'],      // 触发 crash flush 的 reason 白名单（可选）
      onQuit: true,                             // before-quit 时 flush
    },
  },
});
```

> **`api` 与 `rpc` 的自动跳过规则**
> - `api`：HEAD 方法、SDK 自身 endpoint 请求、带 `x-arms-rum-internal` header 的请求一律不采（fetch 与 http/https 两条路径规则一致）
> - `rpc`：仅采 server 端（由 `instrumentTRPC()` 注入的 middleware）；主进程作为 tRPC client 调用云端 HTTP 时由 `api` 采集器负责，无需重复接入

### api 采集器（主进程 HTTP 请求）

`ApiCollector` 在两个层面 hook 主进程的 outbound HTTP 请求，产出 `type='api'` 的资源事件：

| Hook 层 | 覆盖范围 | 说明 |
|---------|----------|------|
| `globalThis.fetch` | 原生 fetch（undici）、axios `adapter: 'fetch'`、基于 fetch 的 SDK（如 openai）| Node >= 18 的内置 fetch |
| `http` / `https` 模块 | **axios 默认 adapter**、got、node-fetch v2 等所有走 Node http 栈的客户端 | patch `http.request` / `http.get` / `https.request` / `https.get` 四个入口 |

**与 fetch 的去重**：undici 实现的 `globalThis.fetch` 不经过 `http` 模块，两条路径天然无重叠；若 `fetch` 被 http 系 polyfill（如 node-fetch v2）替换，其底层 `http.request` 会被自动识别并跳过（`AsyncLocalStorage` 上下文标记），同一请求只上报一次。

**tracing 注入**：两条路径共用同一份 [`tracing` 配置](#tracing-配置) 决策，自动向 outbound 请求注入 `traceparent` 等追踪头。

**细分耗时字段**：事件在 `duration` 之外附带以下度量（与 Browser 端资源事件对齐；无效值自动剔除）：

| 字段 | 说明 |
|------|------|
| `size` | 资源大小（字节数，decoded body） |
| `transfer_size` | 实际网络传输大小（http/https 路径取 Content-Length 近似，chunked 编码时缺省） |
| `dns_duration` | 最后一个请求的 DNS 解析花费的时间 |
| `connect_duration` | 与服务器建立连接花费的时间 |
| `ssl_duration` | TLS 握手花费的时间 |
| `redirect_duration` | 重定向 HTTP 请求花费的时间（仅 fetch 路径） |
| `first_byte_duration` | 等待接收响应的第一个字节所花费的时间 |
| `download_duration` | 下载响应所用的时间 |

数据来源随路径而异：

- **fetch 路径**：匹配 undici 写入 `perf_hooks` 的 resource entry 计算（Node >= 18.2），事件同时附带 `timing_data`（entry 原始 JSON）。entry 在响应 body 读取完成后才产生，body 一直未被消费时等待 2s 后降级——事件仍上报，仅缺细分耗时
- **http/https 路径**：基于 socket 事件（`lookup` / `connect` / `secureConnect`）与响应流锚点计算；上报点为响应 body 下载完成，`duration` 为完整耗时（含下载）

> **已知边界**
> - keep-alive 复用连接不触发 DNS/TCP/TLS 事件，对应指标缺省（与浏览器行为一致）
> - axios 默认依赖 follow-redirects，重定向链路按 hop 产出事件（每 hop 一条）；http/https 路径无 `redirect_duration`
> - 响应 body 一直未被消费的 http/https 请求由 10s 兜底定时器触发上报（无 download 指标）

### memory 采集器（`IMemoryCollectorConfig`）

继承 `ICollectorConfig`，新增以下字段：

| 字段 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `interval` | `number` | `1_800_000`（30min）| 窗口聚合上报间隔 ms。到点输出 `scene='memory_max'` 一条事件并清零累加器。**最小值同样为 30min**，传入更小的值会被向上 clamp 到 30min |
| `onLaunch` | `boolean` | `true` | 是否在 `app ready` 时发出 `state='cold-launch'` `scene='memory'` 启动基线事件 |
| `onInterval` | `boolean` | `true` | 是否启用窗口聚合（`memory_max`）周期采集与上报。关闭后仅保留启动基线、崩溃、退出等单次事件 |
| `onCrash` | `boolean` | `true` | `render-process-gone` / `child-process-gone` 时立即 flush 当前未完成窗口（带 `complete: false`），输出 `state='crash'` `scene='memory'` 事件，累加器**不**清零。仅 reason 命中 `crashReasons` 白名单时触发 |
| `crashReasons` | `string[]` | `['crashed', 'oom', 'abnormal-exit', 'launch-failed', 'integrity-failure']` | 触发 crash flush 的 reason 白名单。`clean-exit` / `killed` 默认排除——隐藏 BrowserView 销毁、HMR 重载、utility 进程正常重启等生命周期事件会以这两种 reason 触发 process-gone，并非真实异常 |
| `onQuit` | `boolean` | `true` | `before-quit` 时同步 flush，输出 `state='before-quit'` `scene='memory'` 事件 |

> **默认未启用**：memory 采集器默认关闭（`checkEnable` 第三个参数为 `false`），需显式 `collectors: { memory: true }` 开启。

> 后台采样间隔固定为 **10s**，不开放配置——避免高频采样在低配机引起抖动。每个 tick 同步调 `app.getAppMetrics()`，仅累加内存中 `{ws_max, sample_count}`，本身不产事件。

**事件映射表**（复用 `RumEventType.APPLICATION`）：

| 触发时机 | state | scene | duration 含义 |
|----------|-------|-------|---------------|
| app ready 即时 | `cold-launch` | `memory` | 当下整体工作集（bytes） |
| 30min 窗口结束 | `schedule` | `memory_max` | 窗口内峰值（bytes） |
| 进程崩溃 | `crash` | `memory` | 崩溃当下整体工作集（bytes） |
| 退出 | `before-quit` | `memory` | 退出当下整体工作集（bytes） |

```typescript
{
  event_type: 'application',
  state: 'cold-launch' | 'schedule' | 'crash' | 'before-quit',
  scene: 'memory' | 'memory_max',
  duration: <bytes>,
  snapshots: '<JSON: MemorySnapshot>',
  context: <RumEventContext>           // 跨端公用环境快照
}
```

`snapshots` 反序列化后字段（按事件类别裁剪）：

| 字段 | 出现于 | 描述 |
|------|--------|------|
| `uptime` | 全部 | 自 app ready 的毫秒数 |
| `trigger` | 全部 | `'cold_launch'` / `'window'` / `'render_gone'` / `'child_gone'` / `'quit'` |
| `main` | 单次值事件（cold-launch / crash / before-quit） | `{ rss, heap_used, heap_total, external, array_buffers }`，bytes，来自 `process.memoryUsage()` |
| `processes[]` | 单次值事件 | 各子进程详情：`type` / `pid` / `name` / `working_set` / `peak_working_set` / `private_bytes` / `cpu_percent` |
| `aggregate` | 单次值事件 | 跨进程聚合：`total_working_set` / `total_peak_working_set` / `renderer_count` / `renderer_total_working_set` / `renderer_max_working_set` |
| `window` | `memory_max`（complete=true）+ crash / before-quit（complete=false）；`cold-launch` 不携带；`sample_count===0` 时省略 | `{ duration_ms, sample_count, complete, total_working_set: { max } }` |
| `crash` | 仅 `state='crash'` 携带 | `{ reason, exit_code?, process_type?, crashed_pid? }`；`process_type` 仅 `child-process-gone` 路径，`crashed_pid` 仅 `render-process-gone` 路径 |


`context` 字段：

| 子结构 | 字段 | 说明 |
|--------|------|------|
| `memory` | `size` / `free` | 设备物理内存总量与当前空闲（bytes），来自 `process.getSystemMemoryInfo()` |
| `device` | `architecture` / `chipset` / `processor_count` / `processor_frequency`（MHz）/ `locale` / `locales` / `timezone` | 取自 `process.arch` + `os.cpus()` + `app.getLocale()` 等 |
| `os` | `kernel_version` / `version_major` | 取自 `os.release()` |
| `network` | `connectivity_status`（`wifi` / `ethernet` / `cellular` / `offline` / `unknown`）/ `connectivity_interfaces` / `is_expensive` / `supports_ipv6` | 来自 `os.networkInterfaces()` 推断 |
| `storage` | `size` / `free` | 应用数据盘容量与剩余（bytes），来自 `fs.statfsSync(app.getPath('userData'))` |

**关闭方式**：

```typescript
// 完全关闭
collectors: { memory: false }

// 关闭启动基线事件（保留周期上报、崩溃、退出）
collectors: { memory: { onLaunch: false } }

// 关闭周期聚合上报（保留启动基线、崩溃、退出）
collectors: { memory: { onInterval: false } }

// 仅关闭 crash 触发（保留周期上报与 before-quit）
collectors: { memory: { onCrash: false } }

// 仅关闭退出 flush
collectors: { memory: { onQuit: false } }
```

### anr 采集器（`IAnrCollectorConfig`）

应用未响应（Application Not Responding）监控，检测主进程与渲染进程的事件循环长时间阻塞，作为 `EXCEPTION` 事件上报：`exception.type='anr'`，`exception.source='main_anr'`（主进程）或 `'renderer_anr'`（渲染进程）。

- **主进程**：独立 `worker_threads` watchdog 通过 IPC 心跳检测主线程阻塞，阻塞时经 V8 Inspector（`connectToMainThread` + `Debugger.pause`）采集主线程调用栈（`snapshots.timing='on-block'`，`snapshots.stack_source='inspector_worker'`）；worker 不可用或 Inspector 超时时自动降级为 `setTimeout` 探针漂移检测，此路径无调用栈、仅 post-block 进程快照（`snapshots.timing='post-block'`，`snapshots.stack_source='unavailable'`）。
- **渲染进程**：主进程作为外部观察者，监测渲染进程经 preload 定时器发送的 IPC 心跳超时；超时后经 `webContents.mainFrame.collectJavaScriptCallStack()` 采集调用栈（`snapshots.timing='on-detect'`）。

> ℹ️ **渲染进程心跳机制**：心跳由 SDK preload 脚本内的 `setInterval` 自主驱动，与渲染进程主线程共用同一事件循环——主线程被同步阻塞时心跳随之停发，从而被主进程 watchdog 判定为 ANR。无需主进程 `executeJavaScript` 注入。
- **防抖与限流**：同源同窗口 `debounceInterval` 内最多 1 条；全局 30min 内最多 5 条；ANR 触发后 5s 恢复静默窗；被抑制次数记入 `snapshots.suppressed_count`。
- **`caused_by`**（仅主进程，启发式推断）：系统可用内存 < 15% → `memory_pressure`；主进程 CPU > 80% → `cpu_saturation`；否则 `event_loop_blocked`。渲染进程恒为 `event_loop_blocked`。

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `enable` | `boolean` | `false` | 默认关闭，需显式 `collectors: { anr: { enable: true } }` 开启 |
| `anrThreshold` | `number` | `5000` | ANR 阈值 ms；下限 3000、上限 30000，越界 clamp |
| `probeInterval` | `number` | `500` | 主进程探针间隔 ms；下限 200、上限 2000；watchdog 检测粒度同此值 |
| `pollInterval` | `number` | `1000` | 渲染进程心跳间隔 ms；下限 500、上限 5000 |
| `captureStackTrace` | `boolean` | `true` | 是否采集渲染进程调用栈。需 Electron >= 34 且用户设置 Feature Flag（见下） |
| `autoInjectDocumentPolicy` | `boolean` | `true` | 是否经 `session.webRequest.onHeadersReceived` 自动注入 `Document-Policy` 响应头。注意：每 session 每 event 仅允许一个 listener，会覆盖既有 listener |
| `debounceInterval` | `number` | `120000` | 同源同窗口防抖间隔 ms；下限 30000、上限 600000 |
| `main` | `boolean` | `true` | 是否监控主进程（仅在 `enable=true` 时生效） |
| `renderer` | `boolean` | `true` | 是否监控渲染进程（仅在 `enable=true` 时生效） |

> ⚠️ **渲染进程调用栈采集需用户设置 Feature Flag**：在 `app.ready` 之前、`import` SDK 之前调用
> `app.commandLine.appendSwitch('enable-features', 'DocumentPolicyIncludeJSCallStacksInCrashReports')`。
> SDK 自动注入 `Document-Policy: include-js-call-stacks-in-crash-reports` 响应头；未设置 flag 时渲染进程 ANR 仍会上报，但 `stack` 为空。调用栈采集需 Electron >= 34，低版本自动降级为无栈事件。

**事件映射表**（复用 `RumEventType.EXCEPTION`）：

| 触发 | source | name | stack | snapshots.timing |
|------|--------|------|-------|------------------|
| 主进程事件循环阻塞 >= anrThreshold | `main_anr` | `MainProcessANR` | Inspector worker 采集（可选，降级时无） | `on-block`（降级 `post-block`） |
| 渲染进程心跳超时 >= anrThreshold | `renderer_anr` | `RendererProcessANR` | `collectJavaScriptCallStack()` 返回值（可选） | `on-detect` |

**最小接入**：

```typescript
// main.ts 顶部（import SDK 之前）
import { app } from 'electron';
app.commandLine.appendSwitch('enable-features', 'DocumentPolicyIncludeJSCallStacksInCrashReports');

import armsRum from '@arms/rum-electron';
await armsRum.init({
  endpoint: '<your-endpoint>',
  collectors: { anr: true },
});
```

---

## browserCollectors 配置（渲染进程采集器）

> 仅在 `autoInject: true` 模式下生效。`autoInject: false` 时，渲染进程的采集器需在 Browser SDK 侧单独配置。

控制自动注入到渲染进程的 Browser SDK 采集器。每个采集器支持 `boolean` 或 `ICollectorConfig` 对象。

| 采集器 | 描述 | 默认值 |
|--------|------|--------|
| `perf` | 页面加载性能 | `true` |
| `webvitals` | Web Vitals 核心指标（LCP / FID / CLS） | `true` |
| `exception` | 未捕获异常 + Promise 拒绝 | `true` |
| `whiteScreen` | 白屏检测 | `true` |
| `api` | HTTP 请求（XHR / Fetch） | `true` |
| `staticResource` | 静态资源加载 | `true` |
| `click` | 用户点击事件 | `true` |
| `action` | 用户交互行为 | `true` |
| `longTask` | 长任务检测（>50ms） | `true` |

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  autoInject: true,
  browserCollectors: {
    perf: true,
    webvitals: true,
    longTask: false,            // 关闭长任务采集
    api: { enable: true },
  },
});
```

---

## tracing 配置

分布式链路追踪。支持 `boolean` 快速开关或 `ITracingOption` 对象。主进程 `ApiCollector` 与 `RpcCollector` 共用同一份决策，自动在 outbound 请求注入追踪头。

| 参数 | 类型 | 描述 | 默认值 |
|------|------|------|--------|
| `enable` | `boolean` | 是否启用链路追踪 | `false` |
| `sample` | `number` | 采样率，取值 **0–100** | `100` |
| `propagatorTypes` | `Array<'tracecontext' \| 'b3' \| 'b3multi' \| 'jaeger' \| 'sw8'>` | 传播协议 | — |
| `allowedUrls` | `Array<MatchOption \| TraceOption>` | 命中规则的 URL/path 才会注入追踪头；未配置时默认全量命中 | — |
| `tracestate` | `boolean` | 是否携带 W3C tracestate | `true` |
| `baggage` | `boolean` | 是否携带 W3C baggage | `false` |

`TraceOption`：`{ match: MatchOption; sampling?: number; propagatorTypes?: PropagatorType[]; tracestate?: boolean; baggage?: boolean; enable?: boolean }`。可对单条 URL 覆盖全局策略，其中 `sampling` 同样为 0–100 区间。

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  tracing: {
    enable: true,
    sample: 50,                                          // 50% 采样
    propagatorTypes: ['tracecontext', 'b3'],
    allowedUrls: [
      'https://api.example.com',
      /\/api\/v\d+\//,
      { match: 'https://payment.example.com', sampling: 100 },  // 支付域强制 100%
    ],
    tracestate: true,
  },
});
```

---

## offlineQueue 配置

离线队列配置。网络请求失败时将事件持久化到磁盘，应用重启或网络恢复后自动重发。默认启用。

| 参数 | 类型 | 描述 | 默认值 |
|------|------|------|--------|
| `enable` | `boolean` | 是否启用离线队列 | `true` |
| `maxAgeDays` | `number` | 最大保留天数，超过后自动淘汰 | `30` |
| `maxQueueSize` | `number` | 最大缓存条数，超过后最早的条目会被淘汰 | `200` |

存储位置：`{userData}/rum-electron-store/offline-queue/`，各条事件为独立 JSON 文件，通过 `queue.json` 索引管理。

重发策略：
- 应用启动 5s 后延迟触发重发，避开启动高峰
- 系统从休眠恢复时触发重发
- 两次重发间隔最小 30s，避免频繁触发
- 重发仍失败时放回队列并停止本轮重发

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  offlineQueue: {
    enable: true,
    maxAgeDays: 7,         // 保留 7 天
    maxQueueSize: 100,     // 最多 100 条
  },
});
```

> **关闭离线队列**：若不需要断网重发能力，可显式关闭：`offlineQueue: { enable: false }`。

---

## filters 配置（事件级过滤）

事件级过滤规则，命中后该事件不上报。与 `collectors.api.filters` / `collectors.rpc.filters`（采集器级，命中即不采）互补。

| 参数 | 类型 | 描述 |
|------|------|------|
| `view` | `MatchOption \| MatchOption[]` | 视图事件 |
| `resource` | `MatchOption \| MatchOption[]` | 资源事件（API、RPC、静态资源） |
| `exception` | `MatchOption \| MatchOption[]` | 异常事件 |

---

## Electron 专属配置

| 参数 | 类型 | 描述 | 默认值 |
|------|------|------|--------|
| `autoInject` | `boolean` | 是否自动注入 Browser SDK 到所有 `BrowserWindow` | `true` |
| `partition` | `string` | 自定义 session partition；与 `BrowserWindow.webPreferences.partition` 对应 | — |
| `spaMode` | `false \| true \| 'auto' \| 'hash' \| 'history'` | SPA 路由追踪模式 | `false` |
| `evaluateApi` | `(request, response, error?) => Promise<IApiBaseAttr>` | 自定义 API/RPC 事件解析回调，返回值经 `reviseApiAttr` 裁剪后合并到事件 | — |
| `parseViewName` | `(url: string) => string` | 自定义页面 name 解析（兜底用 `spaMode` 从 URL 提取） | — |
| `parseResourceName` | `(url: string) => string` | 自定义资源 name 解析；默认取 URL pathname，与 browser SDK 同语义 | — |
| `beforeReport` | `(bundle: RumEventBundle) => any` | 上报前回调，可修改 bundle；同步阻塞会拖慢上报队列 | — |
| `properties` | `Record<string, number \| string>` | 全局自定义属性，附加到所有上报事件 | — |

> **`autoInject` 取舍**
> `autoInject: true` 时 SDK 在 `web-contents-created` → `dom-ready` 时机为每个窗口注入 Browser SDK 脚本。设为 `false` 后需在渲染进程显式 `import '@arms/rum-electron/browser'` 并 `init()`。两种模式不可混用。

> **`spaMode` 取值含义**
> - `false`：禁用 SPA 路由追踪（默认，仅追踪完整页面加载）
> - `true` / `'auto'`：自动检测，优先 hash 后 pathname
> - `'hash'`：Hash 路由模式（如 React HashRouter）
> - `'history'`：History API 路由模式（如 React BrowserRouter）

> **`partition` 注意** 必须与 `BrowserWindow.webPreferences.partition` 值一致。多 partition 场景应使用 `armsRum.registerSession(partition)` 在创建窗口前逐个注册。

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  autoInject: true,
  partition: 'persist:main',
  spaMode: 'hash',
  parseViewName: (url) => {
    const match = url.match(/\/app\/([^/?#]+)/);
    return match ? match[1] : url;
  },
  parseResourceName: (url) => {
    return new URL(url).pathname.replace(/\/\d+(\/|$)/g, '/:id$1');
  },
  beforeReport: (bundle) => {
    console.log('beforeReport', bundle);
    return bundle;  // 返回 undefined 也不会丢弃；需要丢弃请显式处理
  },
  properties: {
    department: 'engineering',
    region: 'cn-hangzhou',
  },
});
```

### evaluateApi：自定义 payload 写入 snapshots

`evaluateApi` 用于把请求/响应内容写入事件。**SDK 不再自动采集 payload**——业务侧按需提取并通过返回的 `snapshots` 字段上报，SDK 自动经 `reviseApiAttr` 裁剪至 5KB。

回调入参随事件类型而异：

| 事件 | `request` | `response` | `error` |
|------|-----------|------------|---------|
| `type='api'`（fetch） | `{ url: string; init: RequestInit }` | `Response \| undefined` | `Error \| undefined` |
| `type='api'`（http/https 模块，如 axios 默认 adapter） | `{ url: string; init: { method, headers } }` | `undefined`（无 `Response` 对象） | `Error \| undefined` |
| `type='rpc'`（tRPC procedure） | `{ path; type; input }` | `{ data }` | `TRPCError \| Error \| undefined` |

返回值结构（`IApiBaseAttr`）：

| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | `0 \| 1` | 业务成功/失败标记 |
| `status_code` | `number \| string` | 覆盖 SDK 推导的状态码 |
| `message` | `string` | 覆盖 SDK 推导的错误信息 |
| `snapshots` | `string` | 写入 `event.snapshots`；SDK 元数据（如 `error.type`、RPC OTel 字段）也在这里，自动经 5KB 裁剪 |
| `properties` | `Record<string, number \| string>` | 写入 `event.properties`；保留给用户业务标签 |

```typescript
armsRum.init({
  endpoint: '<your-endpoint>',
  async evaluateApi(request, response, error) {
    const snapshot: Record<string, unknown> = {};

    // tRPC procedure：request.input / response.data
    if (request && typeof request === 'object' && 'input' in request) {
      snapshot.input = (request as { input?: unknown }).input;
    }
    if (response && typeof response === 'object' && 'data' in response) {
      snapshot.output = (response as { data?: unknown }).data;
    }

    return {
      success: error ? 0 : 1,
      snapshots: JSON.stringify(snapshot),
    };
  },
});
```

> **超时与错误处理** 回调有 50ms 超时硬限，超时或抛错时 SDK 回退到原始事件（不会阻塞上报）。
> 返回的 `snapshots` 会**覆盖** SDK 默认写入的元数据，如需保留 SDK 字段（如 RPC `rpc.system` / `rpc.service` / `rpc.method`），需在返回值中手动合并。

---

## SDK API

### `armsRum.init(config)`

初始化 SDK。必须在 Electron `app.ready` 之前调用，且 SDK 模块本身必须更早被 `import`（顶层会注册 `rum-event` 自定义协议）。

```typescript
init(config: IElectronConfig): Promise<ArmsRum>
```

```typescript
import { app } from 'electron';
import armsRum from '@arms/rum-electron';

armsRum.init({
  endpoint: '<your-endpoint>',
  env: 'prod',
  version: '1.0.0',
});

app.whenReady().then(() => {
  // 创建 BrowserWindow 等
});
```

---

### `armsRum.getConfig()`

获取当前 SDK 配置。

```typescript
getConfig(): IElectronConfig
```

```typescript
const config = armsRum.getConfig();
console.log(config.endpoint, config.env);
```

---

### `armsRum.setConfig()`

动态修改 SDK 配置，支持两种调用方式。

```typescript
setConfig<T extends keyof IElectronConfig>(key: T, value: IElectronConfig[T]): void;
setConfig(config: Partial<IElectronConfig>): void;
```

```typescript
armsRum.setConfig('enable', false);
armsRum.setConfig({ env: 'daily', version: '2.0.0' });
```

---

### `armsRum.registerSession(partition)`

为自定义 partition 的 `BrowserWindow` 注册 RUM preload 脚本。需在 `init()` 之后、对应 `BrowserWindow` 创建之前调用。

```typescript
registerSession(partition: string): Promise<ArmsRum>
```

```typescript
await armsRum.init({ endpoint: '<your-endpoint>' });
await armsRum.registerSession('persist:main');

const win = new BrowserWindow({
  webPreferences: { partition: 'persist:main' },
});
```

> 若 `init()` 时已通过 `partition` 字段声明，则同一 partition 无须再次调用此方法。

---

### `armsRum.instrumentTRPC(t)`

一行接入 tRPC server 端监控：包装 `initTRPC.create()` 的返回值，之后 `t.procedure` 自动带监控 middleware，业务侧 procedure 定义无需修改。

```typescript
instrumentTRPC<T>(t: T): T
```

```typescript
import { initTRPC } from '@trpc/server';
import armsRum from '@arms/rum-electron';

const t = armsRum.instrumentTRPC(initTRPC.create());

export const appRouter = t.router({
  greeting: t.procedure.input(...).query(...),     // 自动采集为 type='rpc' 事件
  createUser: t.procedure.input(...).mutation(...),
});
```

需要按 procedure path 跳过部分调用时，配置 `collectors.rpc.filters`（参考[collectors 配置](#collectors-配置主进程采集器)）。

---

### `armsRum.sendCustom(payload)`

上报自定义事件，必须包含 `type` 和 `name` 两个属性，否则无法上报。属性的具体业务意义可参考下表，实际使用需要自行定义业务语义。

```typescript
sendCustom(payload: RumCustomEvent): void
```

| 参数 | 类型 | 描述 | 是否必填 |
|------|------|------|----------|
| `type` | `string` | 类型 | 是 |
| `name` | `string` | 名称 | 是 |
| `group` | `string` | 分组 | 否 |
| `value` | `number` | 值 | 否 |
| `properties` | `object` | 自定义属性 | 否 |

```typescript
armsRum.sendCustom({
  // 必选
  type: 'CustomEventType1',
  name: 'customEventName2',
  // 可选
  group: 'customEventGroup3',
  value: 111.11,
  properties: {
    prop_msg: 'custom msg',
    prop_num: 1,
  },
});
```

---

### `armsRum.sendView(payload)`

上报自定义 View 性能数据，必须包含 `t1`、`t2`、`t3` 三个指标中的其中之一，否则无法上报。

```typescript
sendView(payload: RumViewEvent): void
```

| 参数 | 类型 | 描述 | 是否必填 | 默认值 |
|------|------|------|----------|--------|
| `type` | `string` | 类型 | 否 | `custom` |
| `t1` | `number` | 自定义性能 | 否 | - |
| `t2` | `number` | 自定义性能 | 否 | - |
| `t3` | `number` | 自定义性能 | 否 | - |
| `properties` | `object` | 自定义属性 | 否 | - |

```typescript
armsRum.sendView({
  type: 'custom',
  t1: 1,
  t2: 2,
  t3: 3,
  properties: {
    prop_msg: 'custom msg',
    prop_num: 1,
  },
});
```

---

### `armsRum.sendException(payload)`

上报自定义异常数据，必须包含 `name` 和 `message` 两个属性，否则无法上报。

```typescript
sendException(payload: RumExceptionEvent | Error): void
```

| 参数 | 类型 | 描述 | 是否必填 |
|------|------|------|----------|
| `name` | `string` | 异常名称 | 是 |
| `message` | `string` | 异常信息 | 是 |
| `file` | `string` | 异常发生文件 | 否 |
| `stack` | `string` | 异常堆栈信息 | 否 |
| `line` | `number` | 异常发生的行数 | 否 |
| `column` | `number` | 异常发生的列数 | 否 |
| `properties` | `object` | 自定义属性 | 否 |

> **说明** 也可直接传入 `Error` 对象，SDK 会自动提取 `name`、`message`、`stack` 字段。

```typescript
armsRum.sendException({
  // 必选
  name: 'customErrorName',
  message: 'custom error message',
  // 可选
  file: 'custom exception filename',
  stack: 'custom exception error.stack',
  line: 1,
  column: 2,
  properties: {
    prop_msg: 'custom msg',
    prop_num: 1,
  },
});
```

---

### `armsRum.sendResource(payload)`

上报自定义资源，必须包含 `name`、`type` 和 `duration` 三个属性，否则无法上报。

```typescript
sendResource(payload: RumResourceEvent): void
```

| 参数 | 类型 | 描述 | 是否必填 |
|------|------|------|----------|
| `name` | `string` | 资源名 | 是 |
| `type` | `string` | 资源类型，例如：css、javascript、xmlhttprequest、fetch、api、image、font、other | 是 |
| `duration` | `number` | 请求耗时（ms） | 是 |
| `success` | `number` | 请求成功状态：1 成功、0 失败、-1 未知 | 否 |
| `method` | `string` | 请求方法 | 否 |
| `status_code` | `number \| string` | 请求状态码 | 否 |
| `message` | `string` | 请求消息 | 否 |
| `url` | `string` | 请求地址 | 否 |
| `trace_id` | `string` | 链路追踪 ID | 否 |
| `properties` | `object` | 自定义属性 | 否 |

```typescript
armsRum.sendResource({
  // 以下必选
  name: 'getListByPage',
  type: 'fetch',
  duration: 800,
  // 以下可选
  url: 'https://www.aliyun.com/data/getListByPage',
  method: 'GET',
  status_code: 200,
  success: 1,
  message: 'success',
  properties: {
    prop_msg: 'custom msg',
    prop_num: 1,
  },
});
```

---

## 渲染进程自定义上报（`window.ArmsRum`）

SDK preload 脚本向每个渲染进程暴露 `window.ArmsRum` 门面，提供与主进程完全同名的四个自定义上报方法：

| 方法 | payload 字段与校验规则 |
|------|------------------------|
| `window.ArmsRum.sendCustom(payload)` | 同 [`armsRum.sendCustom`](#armsrumsendcustompayload)，`type` + `name` 必填 |
| `window.ArmsRum.sendView(payload)` | 同 [`armsRum.sendView`](#armsrumsendviewpayload)，`t1`/`t2`/`t3` 至少其一 |
| `window.ArmsRum.sendException(payload)` | 同 [`armsRum.sendException`](#armsrumsendexceptionpayload)，`name` + `message` 必填，支持直接传 `Error` 实例 |
| `window.ArmsRum.sendResource(payload)` | 同 [`armsRum.sendResource`](#armsrumsendresourcepayload)，`name` + `type` + `duration` 必填 |

调用后 payload 经 IPC 通道（`arms:rum-custom-report`）转发到主进程，由主进程复用与 `armsRum.sendXxx` 相同的校验与组装逻辑完成上报，因此两端语义完全一致。

```typescript
// 渲染进程任意位置
window.ArmsRum?.sendCustom({
  type: 'biz',
  name: 'checkout_click',
  value: 1,
  properties: { skuId: 'SKU-1001' },
});

try {
  riskyOperation();
} catch (e) {
  window.ArmsRum?.sendException(e as Error);   // 直接传 Error 实例
}
```

**与主进程 API 的差异与附加行为**：

| 行为 | 说明 |
|------|------|
| `sendView` 的 `url` 字段 | **由 SDK 自动采集当前页面地址**，payload 中传入的 `url` 会被忽略 |
| 自动附加字段 | 事件自动携带发送窗口的 `view` 信息和 `os.container: 'chromium'` 渲染进程标识 |
| 可用时机 | 由 preload 注入，不依赖 Browser SDK 的 `dom-ready` 注入时机，页面早期脚本即可调用；`autoInject: false` 模式下同样可用 |
| 兼容性 | 兼容 `contextIsolation` 开启/关闭两种场景 |
| init 前调用 | 主进程 `armsRum.init()` 完成前的调用会被静默丢弃 |
| 自定义 partition | 需保证对应 partition 已声明（`init()` 的 `partition` 字段或 `registerSession()`），否则 preload 未注入，`window.ArmsRum` 为 `undefined` |

**TypeScript 类型提示**：在渲染进程任一 `.d.ts` 中引入一次即可获得 `window.ArmsRum` 的全局类型声明：

```typescript
// src/renderer/env.d.ts
import type {} from '@arms/rum-electron/preload';
```
